package com.cdm.domain.dao;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.logging.Level;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import com.cdm.persistence.EntityManagerHelper;

/**
 * A data access object (DAO) providing persistence and search support for
 * Sequence entities. Transaction control of the save(), update() and delete()
 * operations must be handled externally by senders of these methods or must be
 * manually added to each of these methods for data to be persisted to the JPA
 * datastore.
 * 
 * @see com.cdm.persitence.Sequence
 * @author MyEclipse Persistence Tools
 */

public class GenericDaoSupport<T> {

	/**
	 * the target entity class
	 */
	private Class<T> entityClass;

	/**
	 * @return the entityClass
	 */
	public Class<T> getEntityClass() {
		return entityClass;
	}

	public GenericDaoSupport() {
		retrieveEntityType();
	}

	/**
	 * gets the entity type this dao is for
	 */
	@SuppressWarnings("unchecked")
	protected void retrieveEntityType() {
		Type supType = getClass().getGenericSuperclass();
		ParameterizedType paramType = (ParameterizedType) supType;
		entityClass = (Class<T>) paramType.getActualTypeArguments()[0];
	}

	protected EntityManager getEntityManager() {
		return EntityManagerHelper.getEntityManager();
	}

	public EntityManager beginTransaction() {
		EntityManagerHelper.beginTransaction();
		return getEntityManager();
	}

	public EntityManager commit() {
		EntityManagerHelper.commit();
		return getEntityManager();
	}


	/**
	 * Perform an initial save of a previously unsaved Sequence entity. All
	 * subsequent persist actions of this entity should use the #update()
	 * method. This operation must be performed within the a database
	 * transaction context for the entity's data to be permanently saved to the
	 * persistence store, i.e., database. This method uses the
	 * {@link javax.persistence.EntityManager#persist(Object) EntityManager#persist}
	 * operation.
	 * 
	 * <pre>
	 * EntityManagerHelper.beginTransaction();
	 * SequenceDAO.save(entity);
	 * EntityManagerHelper.commit();
	 * </pre>
	 * 
	 * @param entity
	 *            Sequence entity to persist
	 * @throws RuntimeException
	 *             when the operation fails
	 */
	public void save(T entity) {
		EntityManagerHelper.log("saving " + entityClass.getSimpleName() + " instance", Level.INFO, null);
		try {
			getEntityManager().persist(entity);
			EntityManagerHelper.log("save successful", Level.INFO, null);
		} catch (RuntimeException re) {
			EntityManagerHelper.log("save failed", Level.SEVERE, re);
			throw re;
		}
	}

	/**
	 * Delete a persistent Sequence entity. This operation must be performed
	 * within the a database transaction context for the entity's data to be
	 * permanently deleted from the persistence store, i.e., database. This
	 * method uses the
	 * {@link javax.persistence.EntityManager#remove(Object) EntityManager#delete}
	 * operation.
	 * 
	 * <pre>
	 * EntityManagerHelper.beginTransaction();
	 * SequenceDAO.delete(entity);
	 * EntityManagerHelper.commit();
	 * entity = null;
	 * </pre>
	 * 
	 * @param entity
	 *            Sequence entity to delete
	 * @throws RuntimeException
	 *             when the operation fails
	 */
	public void delete(T entity) {
		EntityManagerHelper.log("deleting " + entityClass.getSimpleName() + " instance", Level.INFO, null);
		try {
			// entity = getEntityManager().getReference(entity.getClass(),
			// entity.getName());
			getEntityManager().remove(entity);
			EntityManagerHelper.log("delete successful", Level.INFO, null);
		} catch (RuntimeException re) {
			EntityManagerHelper.log("delete failed", Level.SEVERE, re);
			throw re;
		}
	}

	/**
	 * Persist a previously saved Sequence entity and return it or a copy of it
	 * to the sender. A copy of the Sequence entity parameter is returned when
	 * the JPA persistence mechanism has not previously been tracking the
	 * updated entity. This operation must be performed within the a database
	 * transaction context for the entity's data to be permanently saved to the
	 * persistence store, i.e., database. This method uses the
	 * {@link javax.persistence.EntityManager#merge(Object) EntityManager#merge}
	 * operation.
	 * 
	 * <pre>
	 * EntityManagerHelper.beginTransaction();
	 * entity = SequenceDAO.update(entity);
	 * EntityManagerHelper.commit();
	 * </pre>
	 * 
	 * @param entity
	 *            Sequence entity to update
	 * @returns Sequence the persisted Sequence entity instance, may not be the
	 *          same
	 * @throws RuntimeException
	 *             if the operation fails
	 */
	public T update(T entity) {
		EntityManagerHelper.log("updating instance", Level.INFO, null);
		try {
			T result = getEntityManager().merge(entity);
			EntityManagerHelper.log("update successful", Level.INFO, null);
			return result;
		} catch (RuntimeException re) {
			EntityManagerHelper.log("update failed", Level.SEVERE, re);
			throw re;
		}
	}

	public T get(int id) {
		return _findById(id);
	}

	public T get(long id) {
		return _findById(id);
	}

	public T get(String id) {
		return _findById(id);
	}

	private T _findById(Object id) {
		EntityManagerHelper.log("finding Sequence instance with id: " + id, Level.INFO, null);
		try {
			T instance = getEntityManager().find(entityClass, id);
			return instance;
		} catch (RuntimeException re) {
			EntityManagerHelper.log("find failed", Level.SEVERE, re);
			throw re;
		}
	}

	/**
	 * Find all Sequence entities with a specific property value.
	 * 
	 * @param propertyName
	 *            the name of the Sequence property to query
	 * @param value
	 *            the property value to match
	 * @param rowStartIdxAndCount
	 *            Optional int varargs. rowStartIdxAndCount[0] specifies the the
	 *            row index in the query result-set to begin collecting the
	 *            results. rowStartIdxAndCount[1] specifies the the maximum
	 *            number of results to return.
	 * @return List<Sequence> found by query
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByProperty(String propertyName, final Object value, final int... rowStartIdxAndCount) {
		EntityManagerHelper.log("finding " + entityClass.getSimpleName() + " instance with property: " + propertyName
		        + ", value: " + value, Level.INFO, null);
		try {
			final String queryString = "select model from " + entityClass.getSimpleName() + " model where model."
			        + propertyName + "= :propertyValue";
			Query query = getEntityManager().createQuery(queryString);
			query.setParameter("propertyValue", value);
			if (rowStartIdxAndCount != null && rowStartIdxAndCount.length > 0) {
				int rowStartIdx = Math.max(0, rowStartIdxAndCount[0]);
				if (rowStartIdx > 0) {
					query.setFirstResult(rowStartIdx);
				}

				if (rowStartIdxAndCount.length > 1) {
					int rowCount = Math.max(0, rowStartIdxAndCount[1]);
					if (rowCount > 0) {
						query.setMaxResults(rowCount);
					}
				}
			}
			return query.getResultList();
		} catch (RuntimeException re) {
			EntityManagerHelper.log("find by property name failed", Level.SEVERE, re);
			throw re;
		}
	}

	/**
	 * Find all Sequence entities.
	 * 
	 * @param rowStartIdxAndCount
	 *            Optional int varargs. rowStartIdxAndCount[0] specifies the the
	 *            row index in the query result-set to begin collecting the
	 *            results. rowStartIdxAndCount[1] specifies the the maximum
	 *            count of results to return.
	 * @return List<Sequence> all Sequence entities
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll(final int... rowStartIdxAndCount) {
		EntityManagerHelper.log("finding all " + entityClass.getSimpleName() + " instances", Level.INFO, null);
		try {
			final String queryString = "select model from " + entityClass.getSimpleName() + " model";
			Query query = getEntityManager().createQuery(queryString);
			if (rowStartIdxAndCount != null && rowStartIdxAndCount.length > 0) {
				int rowStartIdx = Math.max(0, rowStartIdxAndCount[0]);
				if (rowStartIdx > 0) {
					query.setFirstResult(rowStartIdx);
				}

				if (rowStartIdxAndCount.length > 1) {
					int rowCount = Math.max(0, rowStartIdxAndCount[1]);
					if (rowCount > 0) {
						query.setMaxResults(rowCount);
					}
				}
			}
			return query.getResultList();
		} catch (RuntimeException re) {
			EntityManagerHelper.log("find all failed", Level.SEVERE, re);
			throw re;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.frails.hibernate.GenericDaoIF#executeQuery(java.lang.String,
	 *      int, java.lang.Object...)
	 */
	@SuppressWarnings("unchecked")
	public List<T> executeQuery(final String condition, final int limit, final Object... params) {
		Query query = getEntityManager().createQuery("from " + entityClass.getSimpleName() + " " + condition);
		for (int i = 0; i < params.length; i++) {
			query.setParameter(i, params[i]);
		}
		query.setMaxResults(limit);

		return (List<T>) query.getResultList();
	}

	/**
	 * @deprecated
	 */
	public List<T> executeQuery(String condition, Object... params) {
		throw new RuntimeException("This Method is Deprecated,Use updateEntity instead.");
	}

	@SuppressWarnings("unchecked")
	public List<T> getByProperty(String propertyName, Object propertyValue) {
		StringBuilder buf = new StringBuilder(60);
		buf.append("from ").append(entityClass.getName()).append(" e where e.").append(propertyName).append(" = ?");

		return (List<T>) getEntityManager().createQuery(buf.toString()).setParameter(1, propertyValue).getResultList();
	}

	@SuppressWarnings("unchecked")
	public List<T> getByProperty(String propertyName, Object propertyValue, String orderBy, boolean asc) {
		StringBuilder buf = new StringBuilder(60);
		buf.append("from ").append(entityClass.getName()).append(" e where e.").append(propertyName).append(
		        " = ? order by ").append(orderBy);
		buf.append(asc ? " asc" : " desc");

		return (List<T>) getEntityManager().createQuery(buf.toString()).setParameter(1, propertyValue).getResultList();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.frails.hibernate.GenericDao#getDataSize()
	 */
	public long getDataSize() {
		Object re = getEntityManager().createQuery("select count(*) from " + entityClass.getName()).getSingleResult();

		if (re instanceof Integer) {
			return (long) ((Integer) re).intValue();
		}

		return (Long) re;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.frails.hibernate.GenericDaoIF#getDataSizeWithProperty(java.lang.String,
	 *      java.lang.Object)
	 */
	public long getDataSizeWithProperty(final String property, final Object value) {
		Object re = getEntityManager().createQuery(
		        "select count(*) from " + entityClass.getName() + " where " + property + "= ?").setParameter(0, value)
		        .getSingleResult();

		if (re instanceof Integer) {
			return (long) ((Integer) re).intValue();
		}

		return (Long) re;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.frails.hibernate.GenericDaoIF#getFirstByProperty(java.lang.String,
	 *      java.lang.Object)
	 */
	public T getFirstByProperty(String propName, Object propVal) {
		List<T> list = getByProperty(propName, propVal);
		if (list.isEmpty()) {
			return null;
		}

		return list.get(0);
	}

	@SuppressWarnings("unchecked")
	public List<T> listAll() {
		return (List<T>) getEntityManager().createQuery("from " + entityClass.getSimpleName()).getResultList();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.frails.hibernate.GenericDaoIF#listAll(java.lang.String,
	 *      boolean)
	 */
	@SuppressWarnings("unchecked")
	public List<T> listAll(String orderBy, boolean asc) {
		StringBuilder buf = new StringBuilder("from ");
		buf.append(entityClass.getName()).append(" order by ").append(orderBy);

		if (asc) {
			buf.append(" asc");
		} else {
			buf.append(" desc");
		}

		return (List<T>) getEntityManager().createQuery(buf.toString()).getResultList();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.frails.hibernate.GenericDaoIF#listByPage(int, int,
	 *      java.lang.String, boolean)
	 */
	@SuppressWarnings("unchecked")
	public List<T> listByPage(final int page, final int span, final String orderBy, final boolean asc) {
		StringBuilder sb = new StringBuilder();
		sb.append("from ").append(entityClass.getSimpleName()).append(" order by ? ?");

		Query query = getEntityManager().createQuery(sb.toString()).setFirstResult((page - 1) * span).setMaxResults(
		        span);
		query.setParameter(0, orderBy);
		if (asc) {
			query.setParameter(1, "asc");
		} else {
			query.setParameter(1, "desc");
		}
		return query.getResultList();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.sf.frails.hibernate.GenericDaoIF#listByPageWithProperty(int,
	 *      int, java.lang.String, java.lang.Object, java.lang.String, boolean)
	 */
	@SuppressWarnings("unchecked")
	public List<T> listByPageWithProperty(final int page, final int span, final String property, final Object value,
	        final String orderBy, final boolean asc) {
		StringBuilder sb = new StringBuilder();
		sb.append("from ").append(entityClass.getSimpleName()).append(" where ?=?  order by ? ?");

		Query query = getEntityManager().createQuery(sb.toString()).setFirstResult((page - 1) * span).setMaxResults(
		        span);

		query.setParameter(0, property);
		query.setParameter(1, value);
		query.setParameter(2, orderBy);
		if (asc) {
			query.setParameter(3, "asc");
		} else {
			query.setParameter(3, "desc");
		}
		return query.getResultList();

	}

}